package cn.imu.config;

import org.dom4j.Attribute;
import org.dom4j.Element;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class BeanFactory {
    private Map<String,Object> beans=new HashMap<>();

    public Map init(String xml) throws  Exception {
        //读取指定的配置文件
        XMLConfig config=new XMLConfig(xml);
        //获取根元素
        Element root=config.getDocument().getRootElement();
        Element foo;
        //遍历bean
        for(Iterator i = root.elementIterator("bean"); i.hasNext();){
            foo=(Element) i.next();
            //获取bean的属性id和class
            Attribute id= foo.attribute("id");
            Attribute clzss= foo.attribute("class");
            //利用Java反射机制，通过class的名称获取Class对象
            Class bean=Class.forName(clzss.getText());
            //获取对应class的信息
            BeanInfo beanInfo = Introspector.getBeanInfo(bean);
            //获取其属性描述
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            //创建一个对象
            Object obj = bean.newInstance();
            // 设置值的方法
            Method method=null;
            //遍历该bean的property属性
            /*
            看这个对象有没有依赖（判断xml的bean标签中是否有property这个子标签/或者判断类是否有属性）
              如果有则注入
              for(Iterator iterator=foo.elementIterator("property");iterator.hasNext();) {
             */

             for(Iterator iterator=foo.elementIterator();iterator.hasNext();) {
                 Element foo2 = (Element) iterator.next();
                 if(foo2!=null&&"property".equals(foo2.getName())) {
                /*
                得到ref的value,通过value得到要注入的对象（从map中取得）
                得到name的值，根据这个值回去一个Filed对象（Filed对象用来取得一个类中的属性）
                这里为了简单默认使用set方法来进行注入，所以要取name值
                通过filed的set方法来注入对象
                 */
                     //获取该property的name属性
                     //// 获取<property>标签的name属性，表示的是要匹配的set方法名，
                     // 但是这里为了简单起见，就默认service对象dao的set方法名与service对象中UserDao属性名一致,都是dao
                     //Attribute name = foo2.attribute("name");
                     String nameValue = foo2.attribute("name").getValue();
                     String value = null;
                     String refValue = null;
                     Object injectObject = null;

                     //获取该property的子元素value或者ref的值
                     if (foo2.attribute("ref").getName().equals("ref")) {
                   /*for(Iterator iterator1=foo2.elementIterator("ref");iterator1.hasNext();){
                       Element node = (Element) iterator1.next();
                       value=node.getText();
                       break;
                   }*/
                         // 获取依赖对象的id
                         refValue = foo2.attribute("ref").getValue();
                         value = refValue.toString();
                         /*  System.out.println(value);*/
                         // 从map中获取要注入的bean
                         injectObject = beans.get(value);

                     } else if (foo2.element("value") != null) {
                         for (Iterator iterator1 = foo2.elementIterator("value"); iterator1.hasNext(); ) {
                             Element node = (Element) iterator1.next();
                             value = node.getText();
                             break;
                         }
                     }
                     // 获取该类属性名为nameValue的属性的Field对象，来进行注入
                     Field field = bean.getDeclaredField(nameValue);
                     // 因为这个属性是私有的，所以要开启暴力反射，将Accessible设置为true
                     field.setAccessible(true);
                     // 将要注入的对象注入到object中，这里object表示的是被注入的对象，
                     // 也就是service，injectObject表示的是注入的对象，也就是dao
                     field.set(obj, injectObject);
                 }

            else if(foo2!=null&&"constructor-arg".equals(foo2.getName())) {// 使用构造方法注入

                    Attribute nameAttribute = foo2.attribute("name");
                    String value = null;
                    String nameValue = null;
                    String refValue = null;
                    Object injectObject = null;
                    // 获取依赖对象的id
                    refValue = foo2.attribute("ref").getValue();
                    if (nameAttribute != null) {
                        nameValue = nameAttribute.getValue();
                    }
                    // 如果XML标签传入了name属性就直接通过注入对象的Field类获取注入对象的
                    if (nameValue != null) {
                        // 获取该类属性名为nameValue的属性的Field对象
                        Field field = bean.getDeclaredField(nameValue);
                        // 直接通过Field对象获取注入对象的Class
                        Class inJectObjectClazz = field.getType();
                        // 通过被注入对象的Class获取构造方法对象，并将注入对象的Class传入，
                        // 这样就可以用这个参数是注入对象的构造方法对象来创建注入dao的service对象
                        Constructor constructor = bean.getConstructor(inJectObjectClazz);
                        // 将注入对象dao传入构造方法中并且生成service的实例对象
                        obj = constructor.newInstance(injectObject);
                    } else {
                        // 如果XML标签没有传入name属性就通过ref属性指定的在spring容器中的bean获取注入对象的Class
                        // 通过spring容器中的bean获取注入对象的Class
                        Class injectObjectClazz = injectObject.getClass();
                        // 由于在spring容器中的bean的类型是UserDaoImpl，而被注入对象中属性的类型是接口UserDao，
                        // 所以会出现两者类型不一致，无法获取正确的构造方法的情况（构造方法参数类型是UserDao）
                        Constructor constructor = bean.getConstructor(injectObjectClazz);
                        // 将注入对象dao传入构造方法中并且生成service的实例对象
                        obj = constructor.newInstance(injectObject);
                    }


                }
            }

                /*for(int k=0;k< propertyDescriptors.length;k++){
                    if(propertyDescriptors[k].getName().equalsIgnoreCase(name.getText())){
                       *//* System.out.println(propertyDescriptors[k]);
                        System.out.println(propertyDescriptors[k].getName());
                        System.out.println(name.getText());*//*
                        method  = propertyDescriptors[k].getWriteMethod();
                        // 利用Java的反射机制调用对象的某个set方法，并将值设置进去
                        //method.invoke(obj,value);//测试value属性
                        //这里仅仅测试成功ref属性
                        method.invoke(obj,injectObject);
                    }
                }*/

            //将对象放入beanMap中，其中key为id值，value为对象
            beans.put(id.getText(),obj);
        }
        return beans;
    }

    /**
     * 通过bean的id获取bean的对象.
     * @param beanName bean的id
     * @return 返回对应对象
     */
    public Object getBean(String beanName){
        Object obj=beans.get(beanName);
        return obj;
    }
}